package com.mstar.qs.common;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.xml.sax.SAXException;

import com.mstar.qs.common.io.XmlUtil;
import com.mstar.qs.common.json.JsonObject;

public class Entry {

	private ReentrantLock lock = new ReentrantLock();

	@Override
	public int hashCode() {
		return super.hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		return super.equals(obj);
	}

	private Entry pre;

	/**
	 * Attributes attached on this entry
	 */
	private Map<String, String> attributes = new HashMap<String, String>();

	/**
	 * Element name with this entry. Value of this field is used to indicate
	 * this entry.
	 */
	private String elementName;

	/**
	 * Text enclosed this entry
	 */
	private String value;

	/**
	 * Subelements stored with each element name as map key
	 */
	private Map<String, List<Entry>> entries = new LinkedHashMap<String, List<Entry>>();

	private String rootName;

	public Entry(String el, Entry pre) throws SAXException, IOException,
			ParserConfigurationException {
		if (el == null)
			throw new NullPointerException("element name is not to be NULL");
		this.elementName = el;
		this.pre = pre;
		// handleIncluding();

	}

	public void handleIncluding() throws SAXException, IOException,
			ParserConfigurationException, DefQsException {
		lock.lock();
		try {
			if (!entries.containsKey("include"))
				return;
			Iterator<Entry> l = entries.remove("include").iterator();
			while (l.hasNext()) {
				handleIncluding(l.next());
				l.remove();
			}
		} finally {
			lock.unlock();
		}
	}

	private void handleIncluding(Entry e) throws SAXException, IOException,
			ParserConfigurationException, DefQsException {
		String file = e.getValue();
		String c = FileUtils.readFileToString(new File(file));
		String ph = e.attribute("placeholder");
		if (ph != null)
			c = Placeholder.placeholderReplacement(c, ph);
		Entry swe = XmlUtil.parse(c);
		Collection<Entry> ies = swe.retrieveSubEntries();
		if (ies != null) {
			for (Entry se : ies) {

				this.addEntry(se);
			}

		}
	}

	/**
	 * convert object to json string
	 */
	public String toString() {
		JsonObject o = new JsonObject(this);
		return o.toJson();
	}

	/**
	 * Add a sub entry
	 * 
	 * @param entry
	 */
	public void addEntry(Entry entry) {
		lock.lock();
		try {
			entry.setPre(this);
			List<Entry> l = entries.get(entry.getElementName());
			if (l == null) {
				l = new ArrayList<Entry>();
				entries.put(entry.getElementName(), l);
			}
			l.add(entry);
		} finally {
			lock.unlock();
		}
	}

	public Entry pre() {
		return pre;
	}

	public void setPre(Entry pre) {
		this.pre = pre;
	}

	/**
	 * retrieve attribute value attached this entry with specified attribute
	 * 
	 * @param key
	 * @return
	 */
	public String attribute(String attribute) {
		String v = attributes.get(attribute);
		return v;
	}

	/**
	 * retrieve attribute of sub entry with specified sub entry name and
	 * attribute key
	 * 
	 * @param attribute
	 * @return
	 */
	public String subAttribute(String subEntryName, String attribute) {
		List<Entry> ses = retrieveSubEntries(subEntryName);
		if (ses == null || ses.size() == 0)
			return null;
		return ses.get(0).attribute(attribute);
	}

	public Map<String, List<Entry>> getEntries() {
		return entries;
	}

	public void setEntries(Map<String, List<Entry>> entries) {
		if (entries == null)
			throw new NullPointerException();
		this.entries = entries;
	}

	public void clear() {
		attributes.clear();
		entries.clear();
	}

	/**
	 * add key value pair. throw NullPointerException if key is null or zero of
	 * length
	 * 
	 * @param key
	 * @param value
	 */
	public void addAttribete(String key, String value) {
		if (key == null || key.trim().length() == 0)
			throw new NullPointerException("attribute:" + key);
		attributes.put(key, value);

	}

	public Map<String, String> getAttributes() {
		return attributes;
	}

	/**
	 * set attributes. throw NullPointerException if specified value is null
	 * 
	 * @param attributes
	 */
	public void setAttributes(Map<String, String> attributes) {
		if (attributes == null)
			throw new NullPointerException();
		this.attributes = attributes;
	}

	/**
	 * return entry name
	 * 
	 * @return
	 */
	public String getElementName() {
		return elementName;
	}

	/**
	 * set new entry name. throw NullPointerException if entry name is null of
	 * zero of length
	 * 
	 * @param elementName
	 */
	public void setElementName(String elementName) {
		if (elementName == null || elementName.trim().length() == 0)
			throw new NullPointerException("element name: " + elementName);
		this.elementName = elementName;
	}

	public String getValue() {
		return value;
	}

	public String text() {
		return value;
	}

	public String subText(String moduleName) {
		Entry e = retrieveSubEntry(moduleName);
		if (e == null)
			return null;
		return e.text();
	}

	public void setValue(String value) {
		this.value = value;
	}

	public boolean empty() {
		return attributes.size() == 0 && entries.size() == 0;
	}

	public List<Entry> secondEntries(String moduleName) {
		return entries.get(moduleName);
	}

	public Entry secondEntrie(String moduleName) {

		List<Entry> le = entries.get(moduleName);
		if (le != null)
			return le.get(0);
		else
			return null;
	}

	/**
	 * Get sub entries with specified sub element name
	 * 
	 * @param elName
	 * @return Sub entries. null if not containing sub entries with with sub
	 *         entry name
	 */
	public List<Entry> retrieveSubEntries(String elName) {
		return entries.get(elName);
	}

	public Collection<Entry> retrieveSubEntries() {
		Set<String> ks = entries.keySet();
		List<Entry> v = new LinkedList<Entry>();
		for (String k : ks) {
			List<Entry> tes = entries.get(k);
			if (tes != null && tes.size() > 0)
				v.addAll(tes);
		}
		return v;
	}

	public Entry retrieveSubEntry(String elName) {
		List<Entry> es = retrieveSubEntries(elName);
		if (es != null && es.size() > 0)
			return es.get(0);
		return null;
	}

	/**
	 * Add specified entry's sub entries to this instance.
	 * 
	 * @param root
	 */
	public void combineSubEntries(Entry root) {
		if (root == null)
			return;
		Collection<Entry> es = root.retrieveSubEntries();
		if (es == null)
			return;
		for (Entry e : es) {
			addEntry(e);
		}
	}

	public void combineSubEntries(String moduleName) {
		lock.lock();
		try {
			List<Entry> ses = this.retrieveSubEntries(moduleName);
			if (ses == null || ses.size() <= 1)
				return;
			Entry r = null;
			Iterator<Entry> it = ses.iterator();
			List<Entry> re = new ArrayList<Entry>();
			while (it.hasNext()) {
				if (r == null)
					r = it.next();
				else {
					Entry t = it.next();
					r.combineSubEntries(t);
					re.add(t);
				}
			}
			if (re.size() > 0)
				ses.removeAll(re);
		} finally {
			lock.unlock();
		}

	}

	public void corssApplication(Entry te) {
		if (te == null)
			return;
		Collection<Entry> es = te.retrieveSubEntries();
		if (es == null || es.size() == 0)
			return;
		for (Entry e : es) {
			boolean ca = false;
			if (e.attribute("cross-application") != null)
				ca = Boolean.getBoolean(e.attribute("cross-application"));
			if (ca)
				addEntry(e);
		}

	}

	public void setRootName(String rootName) {
		this.rootName = rootName;
	}

	public String getRootName() {
		return rootName;
	}
}
